var Amqp = require('../amqp');

function int2bytestr(/** Number */n, /** Number */size)/** String */{
    var s = '';
    for ( var i = 0; i < size; ++i, n >>= 8) {
        s = String.fromCharCode(n & 255) + s;
    }
    return s;
}

var encoder = {
    octet: function(/**Number*/n)/**String*/{
        return int2bytestr(n, 1);
    },

    short: function(/**Number*/n)/**String*/{
        return int2bytestr(n, 2);
    },

    long: function(/**Number*/n)/**String*/{
        return int2bytestr(n, 4);
    },

    longlong: function(/**Number*/n)/**String*/{
        return int2bytestr(n, 8);
    },

    shortstr: function(/**String*/s)/**String*/{
        return this.octet(s.length) + s;
    },

    longstr: function(/**String*/s)/**String*/{
        if ('object' == typeof s) return this.table(s);
        return this.long(s.length) + s;
    },

    table: function(/**Object*/o)/**String*/{
        var s = '';
        for (var k in o) {
            s += this.shortstr(k) + 'S' + this.longstr(o[k]);
        }
        return this.longstr(s);
    }
};

function method(/**Object*/frame)/**String*/{
    var b = 0, bc = 0, payload = '', A = Amqp.Class[frame.method.classId][frame.method.methodId];

    payload += encoder.short(frame.method.classId);
    payload += encoder.short(frame.method.methodId);

    function flushBits(){
        if (!bc) return;
        payload += encoder.octet(b);
        b = 0, bc = 0;
    }

    for (var i = 0, l = A.type.length; i < l; ++i) {
        if ('bit' == A.type[i]) {
            b += (1 << bc++) * frame.method.arguments[A.enum[i]] ? 1 : 0;
            if (8 == bc) flushBits();
        } else {
            flushBits();
            payload += encoder[A.type[i]](frame.method.arguments[A.enum[i]]);
        }
    }
    flushBits();

    return payload;
}

function header(/**Object*/frame){
    var
        classId = frame.method.classId,
        weight = ('childs' in frame) ? frame.childs.length : 0,
        bodySize = frame.body.length;
    var flags = 0, list = '';
    for (var i = 0, l = Amqp.Class[classId].fields.enum.length; i < l;) {
        for (var j = 15; j > 0 || i < l; --j, ++i) {
            var
                field = Amqp.Class[classId].fields.enum[i],
                type  = Amqp.Class[classId].fields.type[i];
            if (field in frame.header.fields) {
                flags += j * Math.pow(2, j);
                list += encoder[type](frame.header.fields[field]);
            }
        }
        if (i < l) {
            flags += 1;
        }
    }
    return encoder.short(classId) + encoder.short(weight) + encoder.longlong(bodySize) +
            encoder.short(flags) + list;
}

function part(frame, type){
    var message = '', payload = '';

    if (Amqp.Const.FrameMethod == type) {
        payload += method(frame);
    } else if (Amqp.Const.FrameHeader == type) {
        payload += header(frame);
        if ('childs' in frame && frame.childs.length) {
            for (var i = 0, l = frame.childs.length; i < l; ++i) {
                payload += encode(frame.childs[i]);
            }
        }
    } else if (Amqp.Const.FrameBody == type) {
        payload += frame.body.toString();
    } else if ('type' in frame) {
        type = frame.type;
    } else {
        type = null;
    }

    if (null === type) return message;

    // type
    message += encoder.octet(type);
    // channel
    message += encoder.short(frame.channel || 0);
    // size
    message += encoder.long(payload.length);
    // payload
    message += payload;
    // end
    message += String.fromCharCode(Amqp.Const.FrameEnd);

    return message;
}

function encode(/**Object*/frame)/**String*/{
    var message = part(frame);

    if ('method' in frame) {
        message += part(frame, Amqp.Const.FrameMethod);
    }
    if ('header' in frame) {
        message += part(frame, Amqp.Const.FrameHeader);
    }
    if ('body' in frame) {
        message += part(frame, Amqp.Const.FrameBody);
    }

    return message;
};

exports.encode = encode;
